package service

import (
	"context"
	"encoding/json"
	"fmt"
	"runtime"
	"time"

	"github.com/nsqio/go-nsq"

	"open/internal/model"
	"open/internal/model/event"
	"open/internal/types"
	"open/pkg/log"
)

// HandlerFunc nsq处理函数
type HandlerFunc func(ctx context.Context, message *nsq.Message) error

// Recovery panic恢复
func Recovery(handlerFunc HandlerFunc) HandlerFunc {
	return func(ctx context.Context, message *nsq.Message) error {
		defer func() {
			if r := recover(); r != nil {
				buf := make([]byte, 64<<10)
				buf = buf[:runtime.Stack(buf, false)]
				errInfo := fmt.Sprintf("worker: panic recovered: %s\n%s", r, buf)
				logger := log.FromContext(ctx)
				logger.Error(errInfo)
			}
		}()
		return handlerFunc(ctx, message)
	}
}

// Logger 日志
func Logger(topic string, handlerFunc HandlerFunc) nsq.HandlerFunc {
	return func(message *nsq.Message) error {
		t := time.Now()
		ctx := context.Background()
		logger := log.FromContext(ctx).With("message_id", fmt.Sprintf("%s", message.ID))
		ctx = log.WithLogger(ctx, logger)
		err := handlerFunc(ctx, message)
		var errMsg string
		if err != nil {
			errMsg = err.Error()
		}
		logger.Infow("nsq",
			"latency", time.Since(t).Seconds(),
			"method", "consume",
			"path", topic,
			"body", string(message.Body),
			"error", errMsg,
		)
		return err
	}
}

func (s *Service) runWorker(topic string, handlerFunc HandlerFunc) {
	if handlerFunc == nil {
		return
	}
	s.dao.RunWorker(context.Background(), topic, Logger(topic, Recovery(handlerFunc)))
}

// HandleTaskSyncQRCode 处理更新二维码任务
func (s *Service) HandleTaskSyncQRCode(ctx context.Context, msg *nsq.Message) (err error) {
	req := new(event.TaskSyncQRCode)
	if err = json.Unmarshal(msg.Body, req); err != nil {
		return err
	}
	err = s.handleTask(ctx, req.JobID, func() error {
		switch req.Platform {
		case types.Wechat:
			err = s.dao.UpdateWechatQRCode(ctx, req.AppID)
		case types.ByteDance:
			err = s.dao.UpdateBytedanceQRCode(ctx, req.AppID)
		case types.Alipay:
			err = s.dao.UpdateAlipayQRCode(ctx, req.AppID)
		default:
			err = fmt.Errorf("未实现")
		}
		return nil
	})
	return
}

// HandleTaskCommitCode 处理提交代码任务
func (s *Service) HandleTaskCommitCode(ctx context.Context, msg *nsq.Message) (err error) {
	req := new(event.TaskCommitCode)
	if err = json.Unmarshal(msg.Body, req); err != nil {
		return err
	}
	err = s.handleTask(ctx, req.JobID, func() error {
		switch req.Platform {
		case types.Wechat:
			err = s.CommitWechatMini(ctx, req.AppID, req.Biz)
		case types.Alipay:
			err = s.CommitAlipayMini(ctx, req.AppID, req.Biz)
		case types.ByteDance:
			err = s.CommitBytedanceMini(ctx, req.AppID, req.Biz)
		}
		if err != nil {
			return err
		}
		return nil
	})
	return
}

// HandleTaskCommitAudit 处理提交审核任务
func (s Service) HandleTaskCommitAudit(ctx context.Context, msg *nsq.Message) (err error) {
	req := new(event.TaskCommitCode)
	if err := json.Unmarshal(msg.Body, req); err != nil {
		return err
	}
	err = s.handleTask(ctx, req.JobID, func() error {
		switch req.Platform {
		case types.Wechat:
			err = s.CommitAuditWechatMini(ctx, req.AppID, req.Biz)
		case types.Alipay:
			err = s.CommitAuditAlipayMini(ctx, req.AppID, req.Biz)
		case types.ByteDance:
			err = s.CommitAuditBytedanceMini(ctx, req.AppID)
		}
		if err != nil {
			return err
		}
		return nil
	})
	return
}

// HandleTaskRelease 处理发布事件
func (s *Service) HandleTaskRelease(ctx context.Context, msg *nsq.Message) (err error) {
	req := new(event.TaskRelease)
	if err := json.Unmarshal(msg.Body, req); err != nil {
		return err
	}
	err = s.handleTask(ctx, req.JobID, func() error {
		switch req.Platform {
		case types.Wechat:
			err = s.ReleaseWechatMini(ctx, req.AppID)
		case types.Alipay:
			err = s.ReleaseAlipayMini(ctx, req.AppID)
		case types.ByteDance:
			err = s.ReleaseBytedanceMini(ctx, req.AppID)
		}
		if err != nil {
			return err
		}
		return nil
	})
	return
}

func (s *Service) HandleTaskUpdateStatus(ctx context.Context, msg *nsq.Message) (err error) {
	req := new(event.TaskUpdateStatus)
	if err := json.Unmarshal(msg.Body, req); err != nil {
		return err
	}
	err = s.handleTask(ctx, req.JobID, func() error {
		switch req.Platform {
		case types.Wechat:
			err = s.UpdateWechatMiniStatus(ctx, req.AppID)
		case types.Alipay:
			err = s.UpdateAlipayMiniStatus(ctx, req.AppID)
		case types.ByteDance:
			return nil
		}
		if err != nil {
			return err
		}
		return nil
	})
	return
}

// HandleTaskSendWechatPubMessage 处理发送微信小程序订阅消息
func (s *Service) HandleTaskSendWechatPubMessage(ctx context.Context, msg *nsq.Message) (err error) {
	req := new(event.TaskSendWechatPubMessage)
	if err := json.Unmarshal(msg.Body, req); err != nil {
		return err
	}
	err = s.handleTask(ctx, req.JobID, func() error {
		if err = s.dao.SendWechatPubMessage(ctx, req); err != nil {
			return err
		}
		return nil
	})
	return
}

// innerRejectWithReason 内部拒绝申请
func (s *Service) innerRejectWithReason(ctx context.Context, appID string, platform int32, reason string) error {
	appVersion, err := s.dao.LockAppVersion(ctx, appID, platform)
	if err != nil {
		return err
	}
	appVersion.RejectReason = reason
	appVersion.Stage = types.StageAuditReject
	appVersion.Status = types.StatusReject
	if err := s.dao.SaveAppVersion(ctx, appVersion); err != nil {
		return err
	}
	return nil
}

func (s *Service) handleTask(ctx context.Context, jobID int64, run func() error) (err error) {
	var schedule *model.Schedule
	if err := s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		schedule, err = s.dao.LockSchedule(ctx, jobID)
		if err != nil {
			return err
		}
		if schedule.JobStatus == types.ScheduleTaskDone || schedule.RetryCount >= 5 || schedule.JobStatus == types.ScheduleTaskRunning {
			return nil
		}
		if schedule.JobStatus == types.ScheduleTaskFailed {
			schedule.RetryCount++
		}
		schedule.JobStatus = types.ScheduleTaskRunning
		if err := s.dao.SaveSchedule(ctx, schedule); err != nil {
			return err
		}
		return nil
	}); err != nil {
		return err
	}
	defer func() {
		if err != nil {
			schedule.JobStatus = types.ScheduleTaskFailed
		} else {
			schedule.JobStatus = types.ScheduleTaskDone
		}
		if err2 := s.dao.SaveSchedule(ctx, schedule); err2 != nil {
			err = err2
			return
		}
	}()
	err = run()
	return
}
